package cn.tedu.sp06.controller;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * RestTemplate是Spring提供的工具,就算不用SpringCloud也可以使用.
 *  效果类似于HttpClient
 *  对Rest Api进行了高度封装 一步就可以发起请求并且将json结果转换为对象
 *
 * Ribbon对RestTemplate又进行了封装,并且提供了负载均衡以及重试的功能
 *  添加负载均衡:->必须有
 *      1.添加依赖
 *      2.对RestTemplate在交给Spring管理时(启动类或者配置类),添加@LoadBalance
 *      3.地址url写成服务的id
 *  添加重试:->可有可无
 *      1.添加spring-retry依赖
 *      2.配置重试参数:
 *      -> MaxAutoRetries:单台服务器的重试次数
 *      -> MaxAutoRetriesNextServer:更换服务器的次数
 *         OkToRetryOnAllOperations:是否对所有请求都重试,默认只对get重试
 *         ConnectTimeout:与远程服务建立连接的等待超时时间
 *      -> ReadTimeout:建立连接,发送请求后,等待接收响应的超时时间
 *    注意:两个超时时间的配置不能再yml中配置,需要在java代码中进行配置
 *
 * Hystrix:系统容错,限流工具
 *  降级:当一个服务调用一个后台服务失败(异常,后台服务不存在,调用超时),可以执行当前服务中的一段"降级代码",来返回"降级结果"
 *      快速失败->当调用后台服务超时,为了不让用户长时间等待,可以直接返回降级结果.
 *      1.添加依赖
 *      2.启动类添加注解@EnableCircuitBreaker
 *      3.添加降级代码
 *          在远程调用代码上,添加@HystrixCommand(fallbackMethod="降级方法名")
 *  注意默认超时时间为1s,1s内没有响应出来正确结果,就执行降级代码
 *  Hystrix超时时间 与 Ribbon总的超时时间要配合设置,Hystrix时间>=Ribbon的总时间,否则Ribbon的重试会失效
 *
 *  熔断:服务过热直接把后台服务断开,限制服务的请求流量
 *      在特定条件下会自动触发熔断:(以下两个请求必须都满足)
 *          条件1:十秒内20次请求
 *          条件2:50%失败,执行了降级代码
 *          ->注意必须先满足条件1,才会判断条件2,10秒内18次请求全失败,则根本不会判断条件2,不会触发熔断
 *
 *      熔断后(断路器打开),所有请求直接执行降级代码,直接返回降级的结果,并且链路不通,不会执行后续的代码
 *
 *      半开状态:断路器打开后几秒,会进入半开状态,会向后台服务尝试发送一次用户请求,如果成功则关闭断路器,恢复正常,
 *          如果失败则会保持断路器打开几秒.之后,又会进入半开状态
 * Hystrix Dashboard: Hystrix仪表盘
 *  actuator->SpringBoot提供的项目监控信息,提供项目的各种监控日志数据
 *      健康状态
 *      Spring容器中所有的对象
 *      SpringMVC所有的路径
 *      env环境变量
 *      ...
 *  Hystrix利用actuator暴露了自己的监控日志,日志断点:Hystrix.stream
 *      1.添加actuator依赖
 *      2.暴露监控端点
 *          暴露所有监控端点:m.e.w.e.i="*"
 *          暴露指定的多个端点:m.e.w.e.i=[health,beans,env]
 *          暴露一个端点:m.e.w.e.i=hystrix.stream
 *      3.访问http://localhost:3001/actuator
 *      里面的数据很难看懂,需要配置一个仪表盘
 *          1.添加Hystrix Dashboard依赖
 *          2.yml
 *              允许抓取日志的服务器列表
 *          3.启动类添加注解@EnableHystrixDashboard
 */
@RestController
@Slf4j
public class RibbonController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/item-service/{orderId}")
    @HystrixCommand(fallbackMethod = "getItemsFB")//降级
    public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
        //调用远程服务,获取订单的商品列表
        //{1} ->RestTemplate定义的一种占位符格式,
        // 第三个参数orderId会填充占位符{1}
//        return restTemplate.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId);
        return restTemplate.getForObject("http://item-service/{1}", JsonResult.class, orderId);
        /*
         * 注册中心的注册表:
         *      item-service----localhost:8001,localhost:8002
         * 从注册表得到item-service对应的多个地址,在多个地址之间来回调用
         */
    }
    @PostMapping("/item-service/decreaseNumber")
    @HystrixCommand(fallbackMethod = "decreaseNumberFB")
    public JsonResult<?> decreaseNumber(@RequestBody List<Item> items){
        return restTemplate.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
    }
    @GetMapping("/user-service/{userId}")
    @HystrixCommand(fallbackMethod = "getUserFB")
    public JsonResult<User> getUser(@PathVariable Integer userId){
        return restTemplate.getForObject("http://user-service:8101/{1}", JsonResult.class,userId);
    }
    @GetMapping("/user-service/{userId}/score")//@RequestParam是?拼接的参数
    @HystrixCommand(fallbackMethod = "addScoreFB")
    public JsonResult<?> addScore(@PathVariable Integer userId,@RequestParam Integer score){
        return restTemplate.getForObject("http://user-service:8101/{1}/score?score={2}", JsonResult.class,userId,score);
    }
    @GetMapping("/order-service/{orderId}")
    @HystrixCommand(fallbackMethod = "getOrderFB")
    public JsonResult<Order> getOrder(@PathVariable String orderId){
        return restTemplate.getForObject("http://order-service:8201/{1}", JsonResult.class,orderId);
    }
    @GetMapping("/order-service")
    @HystrixCommand(fallbackMethod = "addOrderFB")
    public JsonResult<?> addOrder(){
        return restTemplate.getForObject("http://order-service:8201/", JsonResult.class);
    }

    //以下都是降级方法
    public JsonResult<List<Item>> getItemsFB(String orderId) {
        return JsonResult.err().msg("获取订单的商品列表失败");
    }
    public JsonResult<?> decreaseNumberFB(List<Item> items){
        return JsonResult.err().msg("减少库存失败");
    }
    public JsonResult<User> getUserFB(Integer userId){
        return JsonResult.err().msg("获取用户失败");
    }
    public JsonResult<?> addScoreFB(Integer userId,Integer score){
        return JsonResult.err().msg("添加积分失败");
    }
    public JsonResult<Order> getOrderFB(String orderId){
        return JsonResult.err().msg("获取订单失败");
    }
    public JsonResult<?> addOrderFB(){
        return JsonResult.err().msg("添加订单失败");
    }
}
